package com.winner.data.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.Validator;

import org.apache.commons.collections4.ListUtils;
import org.springframework.stereotype.Component;

import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisException;
import com.winner.data.entity.request.UserImport;
import com.winner.data.service.UserService;
import com.winner.data.util.MybatisBatchUtil;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.NoArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;

/**
 * 自定义监听器
 *
 * @author wangxinle5
 * @since 2023-01-16
 */
@Slf4j
@Component
@NoArgsConstructor
public class UserListener extends AnalysisEventListener<UserImport> {

    HashMap<String, UserImport> hm = new HashMap<>();

    UserService userService = SpringUtil.getBean(UserService.class);

    Validator validator = SpringUtil.getBean(Validator.class);

    private String code;

    /**
     * 用构造函数, 仅作为传参
     */
    public UserListener(String code) {
        this.code = code;
    }

    /**
     * 每一行读取完毕会调用
     */
    @Override
    public void invoke(UserImport data, AnalysisContext context) {
        // 校验不通过的行号
        Integer rowIndex = context.readRowHolder().getRowIndex() + 1;
        Set<ConstraintViolation<UserImport>> constraintViolations = validator.validate(data);
        constraintViolations.forEach(constraintViolation -> {
            String errorMsg = constraintViolation.getMessage();
            throw new ExcelAnalysisException(StrUtil.format("上传失败:第{}行{}", rowIndex, errorMsg));
        });
        validData(data, rowIndex);
        String name = data.getName().toUpperCase();
        if (hm.containsKey(name)) {
            throw new ExcelAnalysisException(StrUtil.format("上传失败:第{}行姓名重复, 重复的姓名是 {}", rowIndex, data.getName()));
        }
        hm.put(name, data);
    }


    /**
     * 每个sheet读取完毕会调用
     * 若一分钟导入一次20w的excel, 到第三个，就报堆内存不够了 java.lang.OutOfMemoryError: Java heap space,
     * 对大数据量导入接口限流(5分钟只能请求1次),一个导入完全跑完也就5分钟.
     *
     * -Xms：表示初始化JAVA堆的大小及该进程刚创建出来的时候，他的专属JAVA堆的大小，一旦对象容量超过了JAVA堆的初始容量，JAVA堆将会自动扩容到-Xmx大小。
     * -Xmx：表示java堆可以扩展到的最大值，在很多情况下，通常将-Xms和-Xmx设置成一样的，因为当堆不够用而发生扩容时，会发生内存抖动影响程序运行时的稳定性。
     * -Xss：规定了每个线程虚拟机栈及堆栈的大小，一般情况下，256k是足够的，此配置将会影响此进程中并发线程数的大小。 通常默认大小为1MB~10MB
     * <p>
     * java -Xms1300m -Xmx1300m -jar yourjarfile.jar 在很多情况下，通常将-Xms和-Xmx设置成一样的，因为当堆不够用而发生扩容时，会发生内存抖动影响程序运行时的稳定性。
     * List<List<UserImport>> partition1 = org.apache.commons.collections4.ListUtils.partition(list, 3000); 推荐使用,不会出现递归过深导致栈内存溢出的问题
     * List<List<UserImport>> partition2 = com.google.common.collect包Lists.partition(list, 3000);
     * List<List<UserImport>> partition3 = cn.hutool.core.collection.ListUtil包ListUtil.partition(list, 3000);
     */
    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        log.info("所有数据解析完成！");
        if (CollUtil.isEmpty(hm)) {
            throw new ExcelAnalysisException("上传失败:excel中没有数据");
        }
        ArrayList<UserImport> list = new ArrayList<>(hm.values());
        List<List<UserImport>> partition = ListUtils.partition(list, MybatisBatchUtil.BATCH_SIZE);
        partition.forEach(a -> {
            // 不用单独写一个异步方法类来执行保存, 直接调用 userService.saveImportData(a, code);即可
            userService.saveImportData(a);
        });
        hm.clear();
    }

    /**
     * 自己写代码校验 (不使用spring注解的校验)
     */
    @SneakyThrows
    private void validData(UserImport data, Integer rowIndex) {
        String name = data.getName();
        if (StrUtil.isEmpty(name)) {
            throw new ExcelAnalysisException(StrUtil.format("上传失败:第{}行姓名不能为空", rowIndex));
        }
    }

}
